package com.jaka.framework.core.dingding.base;

import com.alibaba.fastjson.JSON;
import com.jaka.framework.common.core.base.IHttpUtils;
import com.jaka.framework.common.core.exception.EmptyValueException;
import com.jaka.framework.common.core.exception.SignMsgFailedException;
import com.jaka.framework.common.core.exception.SignVerifyFailedException;
import com.jaka.framework.common.core.logging.Logger;
import com.jaka.framework.common.core.logging.LoggerEvent;
import com.jaka.framework.common.core.logging.LoggerFactory;
import com.jaka.framework.common.core.logging.TimeCost;
import com.jaka.framework.core.dingding.config.APIClientConfiguration;
import okhttp3.RequestBody;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;

import static com.jaka.framework.core.dingding.config.APIInterfaceMapping.getSplicingInput;


/**
 * @author ：james.liu
 * @date ：Created in 2021/12/14 16:37
 * @description：
 * @version: 1.0
 */
@SuppressWarnings("all")
public class DefaultAPIActionProxy implements AbstractDefaultAPIActionProxy {

    private Logger logger = LoggerFactory.getLogger(DefaultAPIActionProxy.class);

    private static final String eventName = "default-api-action-proxy";

    private static final DefaultAPIActionProxy instance = new DefaultAPIActionProxy();

    private DefaultAPIActionProxy() {
    }

    /**
     * 描述：API接口动作代理泛型方法<br/>
     *
     * @param input         入参基类
     * @param configuration 核心配置参数
     * @param httpUtils     http请求工具
     * @param clz           返回基类
     * @param requestBody   入参Body
     * @param <I>           基类
     * @param <O>           基类
     * @return
     * @throws SignMsgFailedException    签名失败
     * @throws SignVerifyFailedException 签名校验失败
     * @throws EmptyValueException       参数校验失败
     * @throws Exception                 其他异常
     */
    public <I extends AbstractAPIInput, O extends AbstractAPIResult> O doAction(
            final I input,
            final APIClientConfiguration configuration,
            final IHttpUtils httpUtils,
            final Class<O> clz,
            final RequestBody requestBody
    ) throws SignMsgFailedException, SignVerifyFailedException, EmptyValueException, Exception {
        TimeCost timeCost = new TimeCost();
        O r;
        String accessToken = configuration.getAccessToken();
        accessToken = (StringUtils.isEmpty(accessToken)) ? getAccessToken() : accessToken;
        //校验参数
        input.checkParameter();
        // 获取接口相关参数
        String send = MessageFormat.format(getSplicingInput(input.getCmdId()), accessToken);
        //发起调用
        StringBuilder sb = new StringBuilder();
        String s = sb.append(configuration.getApiUrl()).append(send).toString();
        final String responseBody = httpUtils.postStringResult(s, requestBody);
        logger.bind(new LoggerEvent(eventName).
                append("args", input).
                append("api", input.getCmdId()).
                append("timeCost", timeCost.cost()).
                append("result", responseBody)).info().log();
        if (StringUtils.isEmpty(responseBody)) {
            throw new EmptyValueException("responseBody is null.");
        }
        String json = responseBody;
        r = JSON.parseObject(json, clz);
        return r;

    }


    /**
     * 描述：API接口动作代理泛型方法<br/>
     *
     * @param input         入参基类
     * @param configuration 核心配置参数
     * @param httpUtils     http请求工具
     * @param clz           返回基类
     * @param requestBody   入参Body
     * @param <I>           基类
     * @param <O>           基类
     * @return
     * @throws SignMsgFailedException    签名失败
     * @throws SignVerifyFailedException 签名校验失败
     * @throws EmptyValueException       参数校验失败
     * @throws Exception                 其他异常
     */
    public <I extends AbstractAPIInput, O extends AbstractAPIResult> O doGetAction(
            final I input,
            final APIClientConfiguration configuration,
            final IHttpUtils httpUtils,
            final Class<O> clz,
            final String... splicing
    ) throws SignMsgFailedException, SignVerifyFailedException, EmptyValueException, Exception {
        TimeCost timeCost = new TimeCost();
        O r;
        String accessToken = configuration.getAccessToken();
        accessToken = (StringUtils.isEmpty(accessToken)) ? getAccessToken() : accessToken;
        //校验参数
        input.checkParameter();
        // 获取接口相关参数
        String send = MessageFormat.format(getSplicingInput(input.getCmdId()), splicing);
        //发起调用
        StringBuilder sbs = new StringBuilder();
        String s = sbs.append(configuration.getApiUrl()).append(send).toString();
        final String responseBody = httpUtils.getStringResult(s);
        logger.bind(new LoggerEvent(eventName).
                append("args", input).
                append("api", input.getCmdId()).
                append("timeCost", timeCost.cost()).
                append("result", responseBody)).info().log();
        if (StringUtils.isEmpty(responseBody)) {
            throw new EmptyValueException("responseBody is null.");
        }
        String json = responseBody;
        r = JSON.parseObject(json, clz);
        return r;

    }

    /**
     * 描述：API接口动作代理泛型方法
     *
     * @param input         入参基类
     * @param configuration 核心配置参数
     * @param httpUtils     http请求工具
     * @param clz           返回基类
     * @param <I>           入参基类
     * @param <O>           出参基类
     * @return
     * @throws SignMsgFailedException    签名失败
     * @throws SignVerifyFailedException 签名校验失败
     * @throws EmptyValueException       参数校验失败
     * @throws Exception                 其他异常
     */
    public <I extends AbstractAPIInput, O extends AbstractAPIResult> O doAction(
            final I input,
            final APIClientConfiguration configuration,
            final IHttpUtils httpUtils,
            final Class<O> clz
    ) throws SignMsgFailedException, SignVerifyFailedException, EmptyValueException, Exception {
        TimeCost timeCost = new TimeCost();
        O r;
        // 校验参数
        input.checkParameter();
        StringBuilder sb = new StringBuilder();
        String splicingInput = getSplicingInput(input.getCmdId());
        String send = MessageFormat.format(splicingInput, configuration.getAccessKey(), configuration.getAccessSecret());
        String s = sb.append(configuration.getApiUrl()).append(send).toString();
        String responseBody = httpUtils.getStringResult(s);
        logger.bind(new LoggerEvent(eventName).
                append("args", input).
                append("api", input.getCmdId()).
                append("timeCost", timeCost.cost()).
                append("result", responseBody)).info().log();
        if (StringUtils.isEmpty(responseBody)) {
            throw new EmptyValueException("responseBody is null.");
        }
        String json = responseBody;
        r = JSON.parseObject(json, clz);
        return r;
    }

    public <I extends AbstractAPIInput, O extends AbstractAPIResult> O doAuthAction(
            final I input,
            final APIClientConfiguration configuration,
            final IHttpUtils httpUtils,
            final Class<O> clz,
            final RequestBody requestBody
    ) throws SignMsgFailedException, SignVerifyFailedException, EmptyValueException, Exception {
        TimeCost timeCost = new TimeCost();
        O r;
        // 校验参数
        input.checkParameter();
        StringBuilder sbs = new StringBuilder();
        String splicingInput = getSplicingInput(input.getCmdId());
        String timestamp =String.valueOf( System.currentTimeMillis());
        System.out.println(timestamp);
        String signature = getSignature(configuration.getAccessSecret(), timestamp, null);
        String send = MessageFormat.format(splicingInput, configuration.getAccessKey(),
                timestamp ,signature);
        String s = sbs.append(configuration.getApiUrl()).append(send).toString();
        System.out.println(s);
        String responseBody = httpUtils.postStringResult(s,requestBody);
        logger.bind(new LoggerEvent(eventName).
                append("args", input).
                append("api", input.getCmdId()).
                append("timeCost", timeCost.cost()).
                append("result", responseBody)).info().log();
        if (StringUtils.isEmpty(responseBody)) {
            throw new EmptyValueException("responseBody is null.");
        }
        String json = responseBody;
        r = JSON.parseObject(json, clz);
        return r;
    }


    public static DefaultAPIActionProxy getInstance() {
        return instance;
    }


    public static String getSignature(String appSecret, String timestamp, String suiteTicket) throws UnsupportedEncodingException {
        StringBuilder canonicalString = new StringBuilder();
        canonicalString.append(timestamp);
        if (suiteTicket != null) {
            canonicalString.append("\n").append(suiteTicket);
        }
        String s = computeSignature(appSecret, canonicalString.toString());
        String encode = URLEncoder.encode(s, "UTF-8");
        String urlEncodeSignature = encode.replace("+", "%20").replace("*", "%2A").replace("~", "%7E").replace("/", "%2F");
        return urlEncodeSignature;

    }

    public static String computeSignature(String canonicalString, String secret) {
        try {
            byte[] signData = sign(canonicalString.getBytes("UTF-8"), secret.getBytes("UTF-8"));
            return new String(Base64.encodeBase64(signData, false));
        } catch (UnsupportedEncodingException var3) {
            throw new RuntimeException("Unsupported algorithm: UTF-8", var3);
        }
    }

    private static byte[] sign(byte[] key, byte[] data) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(key, "HmacSHA256"));
            return mac.doFinal(data);
        } catch (NoSuchAlgorithmException var3) {
            throw new RuntimeException("Unsupported algorithm: HmacSHA256", var3);
        } catch (InvalidKeyException var4) {
            throw new RuntimeException("Invalid key: " + key, var4);
        }
    }
}
